import re
from collections import namedtuple
from exceptions import LiquidMissingHandlerError


class Registry:

    def __init__(self, custom_error=None):
        # NOTE: 空间换时间
        self.term_registry = {}
        self.alias_registry = {}
        self.custom_error = custom_error

    def __getattr__(self, term_or_alias):
        handler = self.term_registry.get(term_or_alias) or \
                  self.alias_registry.get(term_or_alias)
        if not handler:
            if self.custom_error:
                raise self.custom_error(term_or_alias)
            raise LiquidMissingHandlerError(term_or_alias)
        return handler

    def __contains__(self, term_or_alias):
        return (
                term_or_alias in self.term_registry or
                term_or_alias in self.alias_registry
        )

    def __str__(self):
        return str(self.term_registry)

    def add_handler_func(self, term, alias, func):
        func_cls = type(
            f'{snake2camel(term)}Handler',
            (object,), {"render": func}
        )
        self.add_handler_cls(term, alias, func_cls)

    def add_handler_cls(self, term, alias, cls):
        self.term_registry[term] = cls
        for a in alias:
            self.alias_registry[a] = cls


def unpack_block(block):
    return block[0].children[0].value, block[0].children[1:]


def camel2snake(camel_case: str):
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', camel_case).lower()


def snake2camel(snake_case: str):
    return re.sub('(_)([a-z0-9])', r'\2'.upper(), snake_case)


class Color:
    HEX_COLOR = re.compile(r"#[0-9a-fA-F]{1,6}")
    HSL_COLOR = re.compile(
        r"hsl\((\d|\d\d|[1-2]\d\d|3[0-5]\d|360),\s*(\d|\d\d|100)%,\s*(\d|\d\d|100)%\)"
    )
    HSLA_COLOR = re.compile(
        r"hsla\((\d|\d\d|[1-2]\d\d|3[0-5]\d|360),\s*(\d|\d\d|100)%,\s*(\d|\d\d|100)%,\s*(0|0\.\d+|1)\)"
    )
    RGB_COLOR = re.compile(
        r"rgb\((\d|\d\d|1\d\d|2[0-4]\d|25[0-5]),\s*(\d|\d\d|1\d\d|2[0-4]\d|25[0-5]),\s*(\d|\d\d|1\d\d|2[0-4]\d|25[0-5])\)"
    )
    RGBA_COLOR = re.compile(
        r"rgba\((\d|\d\d|1\d\d|2[0-4]\d|25[0-5]),\s*(\d|\d\d|1\d\d|2[0-4]\d|25[0-5]),\s*(\d|\d\d|1\d\d|2[0-4]\d|25[0-5]),\s*(0|0\.\d+|1)\)"
    )

    def __init__(self, color: str):
        self.color = self._load_color_to_rgba(color)

    def _load_color_to_rgba(self, color: str):
        rgba = namedtuple("color", ["r", "g", "b", "a"])
        color = color.strip('"\'')
        if self.RGB_COLOR.match(color):
            m = self.RGB_COLOR.match(color)
            r, g, b, a = int(m.group(1)), int(m.group(2)), int(m.group(3)), 0
        elif self.RGBA_COLOR.match(color):
            m = self.RGBA_COLOR.match(color)
            r, g, b, a = int(m.group(1)), int(
                m.group(2)), int(m.group(3)), float(m.group(4))
        elif self.HEX_COLOR.match(color):
            f_hex_color = color + "0" * (7 - len(color))
            r, g, b = list(map(lambda i: int(i, base=16), [f_hex_color[i:i + 2]
                                                           for i in range(1, len(f_hex_color), 2)]))
            a = 0
        elif self.HSL_COLOR.match(color):
            m = self.HSL_COLOR.match(color)
            h, s, l, a = int(m.group(1)), int(m.group(2)) / \
                         100, int(m.group(3)) / 100, 0
            r, g, b = self.hsl_to_rgb(h, s, l)
        elif self.HSLA_COLOR.match(color):
            m = self.HSLA_COLOR.match(color)
            h, s, l, a = int(m.group(1)), int(m.group(2)) / \
                         100, int(m.group(3)) / 100, float(m.group(4))
            r, g, b = self.hsl_to_rgb(h, s, l)
        else:
            r, g, b, a = 0, 0, 0, 0

        return rgba(r, g, b, a)

    @classmethod
    def hsl_to_rgb(cls, h, s, l):

        def _compute(x):
            if x < 1 / 6:
                return p + ((q - p) * 6 * x)
            elif x < 1 / 3:
                return q
            elif 1 / 2 <= x < 2 / 3:
                return p + ((q - p) * 6 * (2 / 3 - x))
            else:
                return p

        if l < 0.5:
            q = l * (1 + s)
        else:
            q = l + s - l * s

        p = 2 * l - q
        h = h / 360

        # R
        if h + 1 / 3 < 0:
            tr = h + 1 / 3 + 1
        elif h + 1 / 3 > 1:
            tr = h + 1 / 3 - 1
        else:
            tr = h + 1 / 3

        # G
        tg = h

        # B
        if h - 1 / 3 < 0:
            tb = h - 1 / 3 + 1
        elif h + 1 / 3 > 1:
            tb = h - 1 / 3 - 1
        else:
            tb = h - 1 / 3

        return _compute(tr), _compute(tg), _compute(tb)

    def rgb_to_hsl(self):
        r = self.color.r / 255
        g = self.color.g / 255
        b = self.color.b / 255
        rgb_max = max(r, g, b)
        rgb_min = min(r, g, b)
        if rgb_max == rgb_min:
            h = 0
        elif rgb_max == r and g >= b:
            h = 60 * (g - b) / (rgb_max - rgb_min)
        elif rgb_max == r and g < b:
            h = 60 * (g - b) / (rgb_max - rgb_min) + 360
        elif rgb_max == g:
            h = 60 * (b - r) / (rgb_max - rgb_min) + 120
        elif rgb_max == b:
            h = 60 * (r - g) / (rgb_max - rgb_min) + 240

        l = (rgb_max + rgb_min) / 2
        if l == 0 or rgb_max == rgb_min:
            s = 0
        elif 0 < l <= 1 / 2:
            s = (rgb_max - rgb_min) / (rgb_max + rgb_min)
        elif l > 1 / 2:
            s = (rgb_max - rgb_min) / (2 - rgb_max - rgb_min)

        return h, s, l

    def to_rgb_string(self):
        if self.color.a:
            return f"rgba({self.color.r}, {self.color.g}, {self.color.b}, {self.color.a})"
        return f"rgb({self.color.r}, {self.color.g}, {self.color.b})"

    def to_hsl_string(self):
        h, s, l = self.rgb_to_hsl()
        if self.color.a:
            return f"hsla({h * 360}, {s * 100}%, {l * 100}%, {self.color.a})"
        return f"hsl({h * 360}, {s * 100}%, {l * 100}%)"

    def to_hex_string(self):
        return f"#{hex(self.color.r)[2:]}{hex(self.color.g)[2:]}{hex(self.color.b)[2:]}"

    def modify(self, channel_name, volume):
        volume = float(volume)
        modified = getattr(self, channel_name) + volume
        if channel_name in ('red', 'green', 'blue'):
            if modified > 255:
                setattr(self, channel_name, 255)
            elif modified < 0:
                setattr(self, channel_name, 0)
            else:
                setattr(self, channel_name, modified)
        elif channel_name == "alpha":
            if modified < 0:
                self.color.a = 0
            elif modified > 1:
                self.color.a = 1
            else:
                self.color.a = modified
        elif channel_name == "hue":
            modified = getattr(self, channel_name) + volume
            if modified < 0:
                setattr(self, channel_name, 0)
            elif modified > 360:
                setattr(self, channel_name, 360)
            else:
                setattr(self, channel_name, volume)
        elif channel_name == "saturation":
            if modified < 0:
                return 0
            elif modified > 100:
                return 100
            else:
                setattr(self, channel_name, volume)
        elif channel_name == "lightness":
            if modified < 0:
                return 0
            elif modified > 100:
                return 100
            else:
                setattr(self, channel_name, volume)
        return modified

    def extract(self, channel_name):
        if channel_name == "red":
            return self.color.r
        elif channel_name == "green":
            return self.color.g
        elif channel_name == "blue":
            return self.color.b
        elif channel_name == "hue":
            return self.rgb_to_hsl()[0]
        elif channel_name == "saturation":
            return self.rgb_to_hsl()[1]
        elif channel_name == "lightness":
            return self.rgb_to_hsl()[2]
        elif channel_name == "alpha":
            return self.color.a

    @property
    def brightness(self):
        """
        formula: ((Red value X 299) + (Green value X 587) + (Blue value X 114)) / 1000
        """
        return (self.color.r * 299 + self.color.g * 587 + self.color.b * 114) / 1000

    @property
    def red(self):
        return self.color.r

    @red.setter
    def red(self, value):
        self.color.r = value

    @property
    def green(self):
        return self.color.g

    @green.setter
    def green(self, value):
        self.color.g = value

    @property
    def blue(self):
        return self.color.b

    @blue.setter
    def blue(self, value):
        self.color.b = value

    @property
    def alpha(self):
        return self.color.a

    @alpha.setter
    def alpha(self, value):
        self.color.a = value
